#include <qua/text.h>
#include <qua/error.h>
#include <boost/optional.hpp>
#include <boost/locale.hpp>
#include <boost/algorithm/string.hpp>
#include <string>

namespace qua
{
    using namespace boost::locale;
    using boost::optional;
    using std::string;
    using std::wstring;

    void assert_char_is_ascii( char ascii_char )
    {
        if( ascii_char & '\x80' )
            throw error( "Required ASCII symbol but come symbol encoded with code page.", __FILE__, __LINE__ );
    }

    class text::data
    {
    public:
        data() {
        }

        ~data() {
        }

        data( wstring const& wide_str )
            : m_wide( wide_str ) {
        }

        data( string const& utf8_str )
            : m_utf8( utf8_str ) {
        }

        data( string const& enc_str, string const& encoding )
            : m_str( enc_str ), m_enc( encoding ) {
        }

        data( wchar_t wide_char )
            : m_wide( wstring( 1u, wide_char ) ) {
        }

        data( char ascii_char )
            : m_utf8( string( 1u, ascii_char ) )
        {
            assert_char_is_ascii( ascii_char );
        }

        data( char enc_char, string const& encoding )
            : m_str( string( 1u, enc_char ) ), m_enc( encoding ) {
        }

        void clear()
        {
            m_wide.reset();
            wide_only();
        }

        void wide_only()
        {
            m_utf8.reset();
            m_str.reset();
            m_enc.reset();
        }

        void utf8_only()
        {
            m_wide.reset();
            m_str.reset();
            m_enc.reset();
        }

        void encstr_only()
        {
            m_wide.reset();
            m_utf8.reset();
        }

        void from_wide( wstring const& wide_str )
        {
            wide_only();
            m_wide = wide_str;
        }

        void from_utf8( string const& utf8_str )
        {
            utf8_only();
            m_utf8 = utf8_str;
        }

        void from_encoding( string const& enc_str, string const& encoding )
        {
            encstr_only();
            m_str = enc_str;
            m_enc = encoding;
        }

        wstring const& to_wide() const
        {
            if( !m_wide )
            {
                if( m_utf8 )
                    m_wide = conv::utf_to_utf<wchar_t>( *m_utf8 );
                else if( m_str && m_enc )
                    m_wide = conv::to_utf<wchar_t>( *m_str, *m_enc );
                else
                    m_wide.reset( wstring() );
            }
            return *m_wide;
        }

        string const& to_utf8() const
        {
            if( !m_utf8 )
            {
                if( m_wide )
                    m_utf8 = conv::utf_to_utf<char>( *m_wide );
                else if( m_str && m_enc )
                    m_utf8 = conv::to_utf<char>( *m_str, *m_enc );
                else
                    m_utf8.reset( string() );
            }
            return *m_utf8;
        }

        string const& to_encoding( string const& encoding ) const
        {
            if( !m_str || !m_enc || *m_enc != encoding )
            {
                m_enc = encoding;
                if( m_wide )
                    m_str = conv::from_utf( *m_wide, *m_enc );
                else if( m_utf8 )
                    m_str = conv::from_utf( *m_utf8, *m_enc );
                else
                    m_str.reset( string() );
            }
            return *m_str;
        }

        wstring& ref_wide()
        {
            to_wide();
            wide_only();
            return *m_wide;
        }

        string ref_utf8()
        {
            to_utf8();
            utf8_only();
            return *m_utf8;
        }

        bool is_wide() const
        {
            return m_wide.is_initialized();
        }

        bool is_utf8() const
        {
            return m_utf8.is_initialized();
        }

        bool is_encoded() const
        {
            return m_str.is_initialized();
        }

        string get_encoding() const
        {
            return m_enc.is_initialized() ? *m_enc : string();
        }

    private:
        mutable optional<wstring> m_wide;
        mutable optional<string>  m_utf8;
        mutable optional<string>  m_str;
        mutable optional<string>  m_enc;
    };

    text::text()
    {
    }

    text::~text()
    {
    }

    text::text( const wchar_t* wide_str )
        : m_data( new data( wide_str ) )
    {
    }

    text::text( const char* utf8_str )
        : m_data( new data( utf8_str ) )
    {
    }

    text::text( const char* enc_str, const char* encoding )
        : m_data( new data( enc_str, encoding ) )
    {
    }

    text::text( wstring const& wide_str )
        : m_data( new data( wide_str ) )
    {
    }

    text::text( string const& utf8_str )
        : m_data( new data( utf8_str ) )
    {
    }

    text::text( string const& enc_str, string const& encoding )
        : m_data( new data( enc_str, encoding ) )
    {
    }

    text::text( wchar_t wide_char )
        : m_data( new data( wide_char ) )
    {
    }

    text::text( char ascii_char )
        : m_data( new data( ascii_char ) )
    {
    }

    text::text( char enc_char, const char* encoding )
        : m_data( new data( enc_char, encoding ) )
    {
    }

    const wchar_t* text::to_wide() const
    {
        return m_data->to_wide().c_str();
    }

    const char* text::to_utf8() const
    {
        return m_data->to_utf8().c_str();
    }

    const char* text::to_encoding( const char* encoding ) const
    {
        return m_data->to_encoding( encoding ).c_str();
    }

    const char* text::to_encoding( string const& encoding ) const
    {
        return m_data->to_encoding( encoding ).c_str();
    }

    text::operator const wchar_t* () const
    {
        return m_data->to_wide().c_str();
    }

    wchar_t& text::operator [] ( int id )
    {
        return m_data->ref_wide()[ id ];
    }

    wchar_t text::operator [] ( int id ) const
    {
        return m_data->to_wide()[ id ];
    }

    void text::from_wide( const wchar_t* wide_str )
    {
        m_data->from_wide( wide_str );
    }

    void text::from_utf8( const char* utf8_str )
    {
        m_data->from_utf8( utf8_str );
    }

    void text::from_encoding( const char* enc_str, const char* encoding )
    {
        m_data->from_encoding( enc_str, encoding );
    }

    void text::from_wide( wstring const& wide_str )
    {
        m_data->from_wide( wide_str );
    }

    void text::from_utf8( string const& utf8_str )
    {
        m_data->from_utf8( utf8_str );
    }

    void text::from_encoding( string const& enc_str, string const& encoding )
    {
        m_data->from_encoding( enc_str, encoding );
    }

    text& text::operator = ( const wchar_t* wide_str )
    {
        from_wide( wide_str );
        return *this;
    }

    text& text::operator = ( const char* utf8_str )
    {
        from_utf8( utf8_str );
        return *this;
    }

    text& text::operator = ( wstring const& wide_str )
    {
        from_wide( wide_str );
        return *this;
    }

    text& text::operator = ( string const& utf8_str )
    {
        from_utf8( utf8_str );
        return *this;
    }

    text::iterator text::begin()
    {
        return text::iterator( this, 0 );
    }

    text::iterator text::end()
    {
        return text::iterator( this, length() );
    }

    text::const_iterator text::begin() const
    {
        return text::const_iterator( this, 0 );
    }

    text::const_iterator text::end() const
    {
        return text::const_iterator( this, length() );
    }

    text::reverse_iterator text::rbegin()
    {
        return text::reverse_iterator( this, 0 );
    }

    text::reverse_iterator text::rend()
    {
        return text::reverse_iterator( this, length() );
    }

    text::const_reverse_iterator text::rbegin() const
    {
        return text::const_reverse_iterator( this, 0 );
    }

    text::const_reverse_iterator text::rend() const
    {
        return text::const_reverse_iterator( this, length() );
    }

    text::const_iterator text::cbegin() const
    {
        return text::const_iterator( this, 0 );
    }

    text::const_iterator text::cend() const
    {
        return text::const_iterator( this, length() );
    }

    text::const_reverse_iterator text::crbegin() const
    {
        return text::const_reverse_iterator( this, 0 );
    }

    text::const_reverse_iterator text::crend() const
    {
        return text::const_reverse_iterator( this, length() );
    }

    text::iterator text::find( wchar_t value )
    {
        wstring const& wide_str = m_data->to_wide();
        auto it = std::find( wide_str.begin(), wide_str.end(), value );
        return iterator( this, static_cast<int>( std::distance( wide_str.begin(), it ) ) );
    }

    text::const_iterator text::find( wchar_t value ) const
    {
        wstring const& wide_str = m_data->to_wide();
        auto it = std::find( wide_str.begin(), wide_str.end(), value );
        return const_iterator( this, static_cast<int>( std::distance( wide_str.begin(), it ) ) );
    }

    text::reverse_iterator text::rfind( wchar_t value )
    {
        wstring const& wide_str = m_data->to_wide();
        auto it = std::find( wide_str.rbegin(), wide_str.rend(), value );
        return reverse_iterator( this, static_cast<int>( std::distance( wide_str.begin(), it.base() ) ) );
    }

    text::const_reverse_iterator text::rfind( wchar_t value ) const
    {
        wstring const& wide_str = m_data->to_wide();
        auto it = std::find( wide_str.rbegin(), wide_str.rend(), value );
        return const_reverse_iterator( this, static_cast<int>( std::distance( wide_str.begin(), it.base() ) ) );
    }

    wchar_t& text::front()
    {
        return m_data->ref_wide().front();
    }

    wchar_t text::front() const
    {
        return m_data->to_wide().front();
    }

    wchar_t& text::back()
    {
        return m_data->ref_wide().back();
    }

    wchar_t text::back() const
    {
        return m_data->to_wide().back();
    }

    int text::length() const
    {
        return static_cast<int>( m_data->to_wide().length() );
    }

    size_t text::size() const
    {
        return m_data->to_wide().size();
    }

    bool text::empty() const
    {
        return m_data->to_wide().empty();
    }

    void text::resize( size_t new_size )
    {
        m_data->ref_wide().resize( new_size );
    }

    void text::reserve( size_t reserve_size )
    {
        m_data->ref_wide().resize( reserve_size );
    }

    void text::clear()
    {
        m_data->ref_wide().clear();
    }

    text::text( lazy<text::data>&& temp_data )
        : m_data( std::move( temp_data ) )
    {
    }

    text::text( text::data* data_ptr )
        : m_data( data_ptr )
    {
    }

    text text::operator + ( text const& other ) const
    {
        return text( new data( m_data->to_wide() + other.m_data->to_wide() ) );
    }

    text text::operator + ( wchar_t wide_char ) const
    {
        return text( new data( m_data->to_wide() + wide_char ) );
    }

    text text::operator + ( char ascii_char ) const
    {
        assert_char_is_ascii( ascii_char );
        return text( new data( m_data->to_utf8() + ascii_char ) );
    }

    text& text::operator += ( text const& other )
    {
        m_data->ref_wide() += other.m_data->to_wide();
        return *this;
    }

    text& text::operator += ( wchar_t wide_char )
    {
        m_data->ref_wide() += wide_char;
        return *this;
    }

    text& text::operator += ( char ascii_char )
    {
        m_data->ref_utf8() += ascii_char;
        return *this;
    }

    void text::insert( text::const_iterator const& into, text const& ins_str )
    {
        wstring& dest = m_data->ref_wide();
        wstring const& src = ins_str.m_data->to_wide();
        dest.insert( dest.begin() + into.index(), src.begin(), src.end() );
    }

    void text::replace( text const& repl, text const& ins )
    {
        boost::replace_all( m_data->ref_wide(), repl.m_data->to_wide(), ins.m_data->to_wide() );
    }

    void text::erase( const_iterator const& from, const_iterator const& upto )
    {
        wstring& dest = m_data->ref_wide();
        dest.erase( dest.begin() + from.index(), dest.begin() + upto.index() );
    }

    text text::substr( const_iterator const& from, const_iterator const& upto ) const
    {
        wstring const& src = m_data->to_wide();
        return text( new data( wstring( src.begin() + from.index(), src.begin() + upto.index() ) ) );
    }

    const wchar_t* text::c_str() const
    {
        return m_data->to_wide().c_str();
    }

    bool text::operator == ( text const& other ) const
    {
        return m_data->to_wide() == other.m_data->to_wide();
    }

    bool text::operator != ( text const& other ) const
    {
        return m_data->to_wide() != other.m_data->to_wide();
    }

    bool text::operator <= ( text const& other ) const
    {
        return m_data->to_wide() <= other.m_data->to_wide();
    }

    bool text::operator >= ( text const& other ) const
    {
        return m_data->to_wide() >= other.m_data->to_wide();
    }

    bool text::operator < ( text const& other ) const
    {
        return m_data->to_wide() < other.m_data->to_wide();
    }

    bool text::operator > ( text const& other ) const
    {
        return m_data->to_wide() > other.m_data->to_wide();
    }

    void text::push_back( wchar_t wide_char )
    {
        m_data->ref_wide().push_back( wide_char );
    }

    void text::push_back( char ascii_char )
    {
        m_data->ref_utf8().push_back( ascii_char );
    }

    bool text::is_wide() const
    {
        return m_data->is_wide();
    }

    bool text::is_utf8() const
    {
        return m_data->is_utf8();
    }

    bool text::is_encoded() const
    {
        return m_data->is_encoded();
    }

    const char* text::get_encoding() const
    {
        return m_data->get_encoding().c_str();
    }

    text operator + ( wchar_t wide_char, text const& right )
    {
        return text( new text::data( wide_char + right.m_data->to_wide() ) );
    }

    void assert_text_iterator_container_is_initialized( text const* container )
    {
        if( !container )
            throw error( "Error: text iterator container is not initialized.", __FILE__, __LINE__ );
    }

    void assert_text_iterator_container_match( text const* first, text const* second )
    {
        if( first != second )
            throw error( "Error: text iterator container mismatch.", __FILE__, __LINE__ );
    }
}
